package util;

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;

public class DynamicImage extends BufferedImage implements I_Timeline, I_Renderable {

	protected int currentFrame;
	protected ArrayList<FrameContent> frames = new ArrayList<FrameContent>();
	protected double frameRate = 30;
	protected Color background = new Color(255, 255, 255, 0);
	protected int width, height;

	protected volatile boolean playing;
	protected Runnable loop = new Runnable() {
		@Override
		public void run () {
			while (playing) {
				if (currentFrame >= frames.size()) {
					break;
				}
				repaint();
				try {
					Thread.sleep((long) (1000 / frameRate));
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				if (!frames.get(currentFrame).isPaused()) {
					currentFrame++;
				}
			}
			playing = false;
		}
	};
	protected Graphics2D g;

	public DynamicImage (int width, int height) {
		this(width, height, E_Quality.DEFAULT);
	}

	public DynamicImage (int width, int height, E_Quality quality) {
		super(width, height, BufferedImage.TYPE_INT_ARGB);
		this.width = width;
		this.height = height;
		setAccelerationPriority(1f);
		g = createGraphics();
		setQuality(quality);
		setBackground(background);
	}

	@Override
	public Graphics2D getGraphics () {
		return g;
	}

	public synchronized void setBackground (Color color) {
		background = color;
		g.setBackground(color);
		g.clearRect(0, 0, width, height);
	}

	public Color getBackgrond () {
		return background;
	}

	public final void setQuality (E_Quality quality) {
		Map<RenderingHints.Key, Object> hints = new HashMap<RenderingHints.Key, Object>();
		switch (quality) {
			case QUALITY:
				hints.put(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
				hints.put(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
				hints.put(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
				hints.put(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_ENABLE);
				hints.put(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
				hints.put(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
				hints.put(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
				hints.put(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_NORMALIZE);
				hints.put(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
				break;
			case MEDIUM:
				hints.put(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_SPEED);
				hints.put(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
				hints.put(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
				hints.put(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_DISABLE);
				hints.put(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_OFF);
				hints.put(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
				hints.put(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
				hints.put(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE);
				hints.put(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
				break;
			case SPEED:
				hints.put(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_SPEED);
				hints.put(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF);
				hints.put(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_SPEED);
				hints.put(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_DISABLE);
				hints.put(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_OFF);
				hints.put(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR);
				hints.put(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_SPEED);
				hints.put(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE);
				hints.put(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_OFF);
				break;
			default:
				hints.put(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_DEFAULT);
				hints.put(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_DEFAULT);
				hints.put(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_DEFAULT);
				hints.put(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_DEFAULT);
				hints.put(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_DEFAULT);
				hints.put(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
				hints.put(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_DEFAULT);
				hints.put(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_NORMALIZE);
				hints.put(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_DEFAULT);
				break;
		}
		g.setRenderingHints(hints);
	}

	public void dispose () {
		flush();
		g.dispose();
	}

	@Override
	public int getWidth () {
		return width;
	}

	@Override
	public int getHeight () {
		return height;
	}

	protected void processActions (OrderedMap<Action, Object> actions) {
		for (OrderedMap.Entry<Action, Object> action : actions) {
			action.key.performAction(action.value);
		}
	}

	@Override
	public void paint (int frame) {
		FrameContent con = frames.get(frame);
		processActions(con.actions);
		flush();
	}

	public synchronized void paintSelf (Graphics g, int x, int y) {
		g.drawImage(this, x, y, null);
	}

	@Override
	public synchronized void repaint (int frame) {
		g.clearRect(0, 0, width, height);
		paint(frame);
	}

	@Override
	public void repaint () {
		repaint(currentFrame);
	}

	@Override
	public void setFrameRate (double frameRate) {
		this.frameRate = frameRate;
	}

	@Override
	public double getFrameRate () {
		return frameRate;
	}

	@Override
	public void play () {
		playing = true;
		Thread looper = new Thread(loop);
		looper.setPriority(Thread.MAX_PRIORITY);
		looper.start();
	}

	/**
	 * lasting is not used currently
	 */
	@Override
	public synchronized void pause (int frame) {
		if (frame < 0) {
			frames.get(currentFrame).setPaused(true);
		} else {
			frames.get(frame).setPaused(true);
		}
	}

	@Override
	public synchronized void resume (int frame) {
		if (frame < 0) {
			frames.get(currentFrame).setPaused(false);
		} else {
			frames.get(frame).setPaused(false);
		}
	}

	@Override
	public void stop () {
		// TODO Auto-generated method stub

	}

	@Override
	public void insertBlankFrame (int i) {
		// TODO Auto-generated method stub
		insertFrame(i, new FrameContent());
	}

	public void addBlankFrame () {
		addFrame(new FrameContent());
	}

	public <T extends FrameContent> void insertFrame (int i, T content) {
		frames.add(i, content);
	}

	public <T extends FrameContent> void addFrame (T content) {
		frames.add(content);
	}

	public void addFrames (FrameContent[] contents) {
		for (FrameContent c : contents) {
			addFrame(c);
		}
	}

	@Override
	public <T extends FrameContent> void setFrameContent (int i, T content) {
		// TODO Auto-generated method stub
		content.setContent(content);
	}

	@Override
	public FrameContent getFrameContent (int i) {
		// TODO Auto-generated method stub
		return frames.get(i);
	}

	@Override
	public void deleteFrame (int i) {
		// TODO Auto-generated method stub
		frames.remove(i);
	}

}
